手写 Promise
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(fn) {
const that = this;
that.state = PENDING;
that.value = null;
// `resolvedCallbacks` 和 `rejectedCallbacks` 用于保存 `then` 中的回调
// 因为当执行完`Promise` 时状态可能还是等待中,这时候应该把`then` 中的回调保存起来用于状态改变时使用
that.resolvedCallbacks = [];
that.rejectedCallbacks = [];
function resolve(value) {
// 对于 `resolve` 函数来说,首先需要判断传入的值是否为 `Promise` 类型
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
// 为了保证函数执行顺序,需要将两个函数体代码使用 `setTimeout` 包裹起来
setTimeout(() => {
if (that.state === PENDING) {
that.state = RESOLVED;
that.value = value;
that.resolvedCallbacks.map((cb) => cb(that.value));
}
}, 0);
}
function reject(value) {
setTimeout(() => {
if (that.state === PENDING) {
that.state = REJECTED;
that.value = value;
that.rejectedCallbacks.map((cb) => cb(that.value));
}
}, 0);
}
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
const that = this;
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
onRejected =
typeof onRejected === "function"
? onRejected
: (r) => {
throw r;
};
if (that.state === state.padding) {
that.resolvedCallbacks.push(onFulfilled);
that.rejectedCallbacks.push(onRejected);
}
if (that.state === state.resolved) {
onFulfilled(that.value);
}
if (that.state === state.rejected) {
onRejected(that.value);
}
};
实现一个符合 Promise/A+ 规范的 Promise
接下来继续改造 then 函数中的代码,首先我们需要新增一个变量 promise2,因为每个 then 函数都需要返回一个新的 Promise 对象,该变量用于保存新的返回对象,然后我们先来改造判断等待态的逻辑
if (that.state === PENDING) {
return (promise2 = new MyPromise((resolve, reject) => {
that.resolvedCallbacks.push(() => {
try {
const x = onFulfilled(that.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
that.rejectedCallbacks.push(() => {
try {
const x = onRejected(that.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
}));
}
- 首先我们返回了一个新的
Promise对象,并在Promise中传入了一个函数 - 函数的基本逻辑还是和之前一样,往回调数组中
push函数 - 同样,在执行函数的过程中可能会遇到错误,所以使用了
try...catch包裹 - 规范规定,执行
onFulfilled或者onRejected函数时会返回一个x,并且执行Promise解决过程,这是为了不同的Promise都可以兼容使用,比如 JQuery 的Promise能兼容 ES6 的Promise
接下来我们改造判断执行态的逻辑
if (that.state === RESOLVED) {
return (promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
const x = onFulfilled(that.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
}
- 其实大家可以发现这段代码和判断等待态的逻辑基本一致,无非是传入的函数的函数体需要异步执行,这也是规范规定的
- 对于判断拒绝态的逻辑这里就不一一赘述了,留给大家自己完成这个作业
最后,当然也是最难的一部分,也就是实现兼容多种 Promise 的 resolutionProcedure 函数
function resolutionProcedure(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError("Error"));
}
}
首先规范规定了 x 不能与 promise2 相等,这样会发生循环引用的问题,比如如下代码
let p = new MyPromise((resolve, reject) => {
resolve(1);
});
let p1 = p.then((value) => {
return p1;
});
然后需要判断 x 的类型
if (x instanceof MyPromise) {
x.then(function (value) {
resolutionProcedure(promise2, value, resolve, reject);
}, reject);
}
这里的代码是完全按照规范实现的。如果 x 为 Promise 的话,需要判断以下几个情况:
- 如果
x处于等待态,Promise需保持为等待态直至x被执行或拒绝 - 如果
x处于其他状态,则用相同的值处理Promise
当然以上这些是规范需要我们判断的情况,实际上我们不判断状态也是可行的。
接下来我们继续按照规范来实现剩余的代码
let called = false;
if (x !== null && (typeof x === "object" || typeof x === "function")) {
try {
let then = x.then;
if (typeof then === "function") {
then.call(
x,
(y) => {
if (called) return;
called = true;
resolutionProcedure(promise2, y, resolve, reject);
},
(e) => {
if (called) return;
called = true;
reject(e);
}
);
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
- 首先创建一个变量
called用于判断是否已经调用过函数 - 然后判断
x是否为对象或者函数,如果都不是的话,将x传入resolve中 - 如果
x是对象或者函数的话,先把x.then赋值给then,然后判断then的类型,如果不是函数类型的话,就将x传入resolve中 - 如果
then是函数类型的话,就将x作为函数的作用域this调用之,并且传递两个回调函数作为参数,第一个参数叫做resolvePromise,第二个参数叫做rejectPromise,两个回调函数都需要判断是否已经执行过函数,然后进行相应的逻辑 - 以上代码在执行的过程中如果抛错了,将错误传入
reject函数中
Promise/A+ 规范
遵循下面几个规则:
- 如果可选参数不为函数时应该被忽略;
- 两个函数都应该是异步执行的,即放入事件队列等待下一轮 tick,而非立即执行;
- 当调用 onFulfilled 函数时,会将当前 Promise 的值作为参数传入;
- 当调用 onRejected 函数时,会将当前 Promise 的失败原因作为参数传入;
- then 函数的返回值为 Promise。
Promise 状态
Promise 的 3 个状态分别为 pending、fulfilled 和 rejected。
Promise 解决过程
Promise 解决过程是一个抽象的操作,即接收一个 promise 和一个值 x,目的就是对 Promise 形式的执行结果进行统一处理。需要考虑以下 3 种情况。
情况 1: x 等于 promise
抛出一个 TypeError 错误,拒绝 promise。
情况 2:x 为 Promise 的实例
如果 x 处于等待状态,那么 promise 继续等待至 x 执行或拒绝,否则根据 x 的状态执行/拒绝 promise。
情况 3:x 为对象或函数
该情况的核心是取出 x.then 并调用,在调用的时候将 this 指向 x。将 then 回调函数中得到结果 y 传入新的 Promise 解决过程中,形成一个递归调用。其中,如果执行报错,则以对应的错误为原因拒绝 promise。
这一步是处理拥有 then() 函数的对象或函数,这类对象或函数我们称之为“thenable”。注意,它只是拥有 then() 函数,并不是 Promise 实例。
情况 4:如果 x 不为对象或函数
以 x 作为值,执行 promise。
Promise 实现
Promise 是单次执行的,所以需要判断状态为 PENDING 的时候再执行函数 resolve() 或函数 reject() 。同时 Promise 的内部异常不能直接抛出,所以要进行异常捕获。
then() 被调用时应该返回一个新的 Promise,所以在上面的 3 种状态的处理逻辑中,都应该创建并返回一个 Promise 实例。以执行状态为例,可以改成下面的样子。
case FULFILLED:
promise = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
onFulfilled(self.value);
} catch (e) {
reject(e)
}
})
});
break;
同时,它带来的另一个效果是支持链式调用。在链式调用的情况下,如果 Promise 实例处于等待状态,那么需要保存多个 resolve() 或 reject() 函数,所以 onFulfilledFn 和 onRejectedFn 应该改成数组。
case PENDING:
promise = new Promise(function (resolve, reject) {
self.onFulfilledFn.push(function () {
try {
onFulfilled(self.value);
} catch (e) {
reject(e)
}
});
self.onRejectedFn.push(function () {
try {
onRejected(self.reason);
} catch (e) {
reject(e)
}
})
});
break;
对应的,Promise 构造函数中应该初始化属性 onFulfilledFn 和 onRejectedFn 为数组,同时 resolve() 和 reject() 函数在改变状态时应该调用这个数组中的函数,并且这个调用过程应该是异步的。
function Promise(execute) {
// ...
self.onFulfilledFn = [];
self.onRejectedFn = [];
// ...
function resolve(value) {
setTimeout(function () {
// ...
self.onFulfilledFn.forEach(function (f) {
f(self.value);
});
});
}
function reject(reason) {
setTimeout(function () {
// ...
self.onRejectedFn.forEach(function (f) {
f(self.reason);
});
});
}
}
resolvePromise() 函数
前面提到解决过程函数有两个参数及 3 种情况,先来考虑第 1 种情况,promise 与 x 相等,应该直接抛出 TypeError 错误:
function resolvePromise(promise, x) {
if (promise === x) {
return reject(new TypeError("x 不能与 promise 相等"));
}
}
情况 2,x 为 Promise 的实例,应该尝试让 promise 接受 x 的状态,怎么接受呢?
直接改变 promise 状态肯定是不可取的,首先状态信息属于内部变量,其次也无法调用属性 onResolvedFn 和 onFulfilledFn 中的待执行函数。所以必须要通过调用 promise 在构造时的函数 resolve() 和 reject() 来改变。
如果 x 处于等待状态,那么 promise 继续保持等待状态,等待解决过程函数 resolvePromise() 执行,否则应该用相同的值执行或拒绝 promise。我们无法从外部拒绝或执行一个 Promise 实例,只能通过调用构造函数传入的 resolve() 和 reject() 函数来实现。所以还需要把这两个函数作为参数传递到 resolvePromise 函数中。
在函数 resolvePromise() 内部加上情况 2 的判断,代码如下:
function resolvePromise(promise, x, resolve, reject) {
...
if (x instanceof Promise) {
if (x.state === FULFILLED) {
resolve(x.value)
} else if (x.state === REJECTED) {
reject(x.reason)
} else {
x.then(function (y) {
resolvePromise(promise, y, resolve, reject)
}, reject)
}
}
}
再来实现情况 3,将 x.then 取出然后执行,并将执行结果放入解决过程函数 resolvePromise() 中。 考虑到 x 可能只是一个 thenable 而非真正的 Promise,所以在调用 then() 函数的时候要设置一个变量 excuted 避免重复调用。同时记得在执行时添加异常捕获并及时拒绝当前 promise。
if (x !== null && (typeof x === "object" || typeof x === "function")) {
var executed;
try {
var then = x.then;
if (typeof then === "function") {
then.call(
x,
function (y) {
if (executed) return;
executed = true;
return resolvePromise(promise, y, resolve, reject);
},
function (e) {
if (executed) return;
executed = true;
reject(e);
}
);
} else {
resolve(x);
}
} catch (e) {
if (executed) return;
executed = true;
reject(e);
}
}
情况 4 就很简单了,直接把 x 作为值执行。
resolve(x);
手写 Promise 实现
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
// promise 接收一个函数参数,该函数会立即执行
function Promise(fn) {
let that = this;
that.currentState = PENDING;
that.value = undefined;
// 用于保存 then 中的回调,只有当 promise
// 状态为 pending 时才会缓存,并且每个实例至多缓存一个
that.resolvedCallbacks = [];
that.rejectedCallbacks = [];
that.resolve = function (value) {
if (value instanceof Promise) {
// 如果 value 是个 Promise,递归执行
return value.then(that.resolve, that.reject);
}
setTimeout(() => {
// 异步执行,保证执行顺序
if (that.currentState === PENDING) {
that.currentState = RESOLVED;
that.value = value;
that.resolvedCallbacks.forEach((cb) => cb());
}
});
};
that.reject = function (reason) {
setTimeout(() => {
// 异步执行,保证执行顺序
if (that.currentState === PENDING) {
that.currentState = REJECTED;
that.value = reason;
that.rejectedCallbacks.forEach((cb) => cb());
}
});
};
// 用于解决以下问题
// new Promise(() => throw Error('error))
try {
fn(that.resolve, that.reject);
} catch (e) {
that.reject(e);
}
}
Promise.prototype.then = function (onResolved, onRejected) {
var self = this;
// 规范 2.2.7,then 必须返回一个新的 promise
var promise2;
// 规范 2.2.onResolved 和 onRejected 都为可选参数
// 如果类型不是函数需要忽略,同时也实现了透传
// Promise.resolve(4).then().then((value) => console.log(value))
onResolved = typeof onResolved === "function" ? onResolved : (v) => v;
onRejected =
typeof onRejected === "function"
? onRejected
: (r) => {
throw r;
};
if (self.currentState === RESOLVED) {
return (promise2 = new Promise(function (resolve, reject) {
// 规范 2.2.4,保证 onFulfilled,onRejected 异步执行
// 所以用了 setTimeout 包裹下
setTimeout(function () {
try {
var x = onResolved(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
}
if (self.currentState === REJECTED) {
return (promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
// 异步执行onRejected
try {
var x = onRejected(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
}
if (self.currentState === PENDING) {
return (promise2 = new Promise(function (resolve, reject) {
self.resolvedCallbacks.push(function () {
// 考虑到可能会有报错,所以使用 try/catch 包裹
try {
var x = onResolved(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
self.rejectedCallbacks.push(function () {
try {
var x = onRejected(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
}));
}
};
// 规范 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
// 规范 2.3.1,x 不能和 promise2 相同,避免循环引用
if (promise2 === x) {
return reject(new TypeError("Error"));
}
// 规范 2.3.2
// 如果 x 为 Promise,状态为 pending 需要继续等待否则执行
if (x instanceof Promise) {
if (x.currentState === PENDING) {
x.then(function (value) {
// 再次调用该函数是为了确认 x resolve 的
// 参数是什么类型,如果是基本类型就再次 resolve
// 把值传给下个 then
resolutionProcedure(promise2, value, resolve, reject);
}, reject);
} else {
x.then(resolve, reject);
}
return;
}
// 规范 2.3.3.3.3
// reject 或者 resolve 其中一个执行过得话,忽略其他的
let called = false;
// 规范 2.3.3,判断 x 是否为对象或者函数
if (x !== null && (typeof x === "object" || typeof x === "function")) {
// 规范 2.3.3.2,如果不能取出 then,就 reject
try {
// 规范 2.3.3.1
let then = x.then;
// 如果 then 是函数,调用 x.then
if (typeof then === "function") {
// 规范 2.3.3.3
then.call(
x,
(y) => {
if (called) return;
called = true;
// 规范 2.3.3.3.1
resolutionProcedure(promise2, y, resolve, reject);
},
(e) => {
if (called) return;
called = true;
reject(e);
}
);
} else {
// 规范 2.3.3.4
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
// 规范 2.3.4,x 为基本类型
resolve(x);
}
}
/**
* Promise.all Promise进行并行处理
* 参数: promise对象组成的数组作为参数
* 返回值: 返回一个Promise实例
* 当这个数组里的所有promise对象全部变为resolve状态的时候,才会resolve。
*/
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let done = gen(promises.length, resolve);
promises.forEach((promise, index) => {
promise.then((value) => {
done(index, value);
}, reject);
});
});
};
function gen(length, resolve) {
let count = 0;
let values = [];
return function (i, value) {
values[i] = value;
if (++count === length) {
console.log(values);
resolve(values);
}
};
}
/**
* Promise.race
* 参数: 接收 promise对象组成的数组作为参数
* 返回值: 返回一个Promise实例
* 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理(取决于哪一个更快)
*/
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
promise.then(resolve, reject);
});
});
};
// 用于promise方法链时 捕获前面onFulfilled/onRejected抛出的异常
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
};
Promise.resolve = function (value) {
return new Promise((resolve) => {
resolve(value);
});
};
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
};
链式调用实现
光是实现了异步操作可不行,我们常常用到 new Promise().then().then() 这样的链式调用来解决回调地狱。
规范如何定义 then 方法:
- 每个 then 方法都返回一个新的 Promise 对象(原理的核心)
- 如果 then 方法中显示地返回了一个 Promise 对象就以此对象为准,返回它的结果
- 如果 then 方法中返回的是一个普通值(如 Number、String 等)就使用此值包装成一个新的 Promise 对象返回。
- 如果 then 方法中没有 return 语句,就视为返回一个用 Undefined 包装的 Promise 对象
- 若 then 方法中出现异常,则调用失败态方法(reject)跳转到下一个 then 的 onRejected
- 如果 then 方法没有传入任何回调,则继续向下传递(值的传递特性) 总的来说就是不论何时 then 方法都要返回一个 Promise,这样才能调用下一个 then 方法。
实现 Promise 的常用 API
实现 Promise.finally
它就是一个语法糖,在当前 promise 实例执行完 then 或者 catch 后,均会触发。Promise.prototype.finally 的执行与 promise 实例的状态无关,不依赖于 promise 的执行后返回的结果值。其传入的参数是函数对象。 实现思路:
- 考虑到 promise 的 resolver 可能是个异步函数,因此 finally 实现中,要通过调用实例上的 then 方法,添加 callback 逻辑
- 成功透传 value,失败透传 error
Promise.prototype.finally = function (cb) {
return this.then(
(value) => Promise.resolve(cb()).then(() => value),
(error) =>
Promise.resolve(cb()).then(() => {
throw error;
})
);
};
实现 Promise.all
Promise.all(iterators)返回一个新的 Promise 实例。iterators 中包含外界传入的多个 promise 实例。
对于返回的新的 Promise 实例,有以下两种情况:
- 如果传入的所有 promise 实例的状态均变为
fulfilled,那么返回的 promise 实例的状态就是fulfilled,并且其 value 是 传入的所有 promise 的 value 组成的数组。 - 如果有一个 promise 实例状态变为了
rejected,那么返回的 promise 实例的状态立即变为rejected。
实现思路:
- 传入的参数不一定是数组对象,可以是"遍历器"
- 传入的每个实例不一定是 promise,需要用
Promise.resolve()包装 - 借助"计数器",标记是否所有的实例状态均变为
fulfilled
Promise.all = function (iterators) {
const promises = Array.from(iterators);
const num = promises.length;
const resolvedList = new Array(num);
let resolvedNum = 0;
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
// 保存这个 promise 实例的 value
resolvedList[index] = value;
// 通过计数器,标记是否所有实例均 fulfilled
if (++resolvedNum === num) {
resolve(resolvedList);
}
})
.catch(reject);
});
});
};
实现 Promise.race
在代码实现前,我们需要先了解 Promise.race 的特点:
- Promise.race 返回的仍然是一个 Promise. 它的状态与第一个完成的 Promise 的状态相同。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个 Promise 是哪一种状态。
- 如果传入的参数是不可迭代的,那么将会抛出错误。
- 如果传的参数数组是空,那么返回的 promise 将永远等待。
- 如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。
Promise.race = function (promises) {
// promises 必须是一个可遍历的数据结构,否则抛错
return new Promise((resolve, reject) => {
if (typeof promises[Symbol.iterator] !== "function") {
//真实不是这个错误
Promise.reject("args is not iteratable!");
}
if (promises.length === 0) {
return;
} else {
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then(
(data) => {
resolve(data);
return;
},
(err) => {
reject(err);
return;
}
);
}
}
});
};
promise.then(...).catch(...);与 promise.then(..., ...); 并不等价, 尤其注意当 promise.then(...).catch(...); 中的 then 会抛异常的情况下。
Promise.myRace = function (iterators) {
const promises = Array.from(iterators);
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
Promise.resolve(promise).then(resolve).catch(reject);
});
});
};
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
if (Array.isArray(promises)) {
if (promises.length === 0) return resolve(promises);
promises.forEach((item) => {
Promise.resolve(item).then(
(value) => resolve(value),
(reason) => reject(reason)
);
});
} else return reject(new TypeError("Argument is not iterable"));
});
};
实现 Promise.any
Promise.any(iterators)的传参和返回值与Promise.all相同。
如果传入的实例中,有任一实例变为fulfilled,那么它返回的 promise 实例状态立即变为fulfilled;如果所有实例均变为rejected,那么它返回的 promise 实例状态为rejected。
Promise.all与Promise.any的关系,类似于,Array.prototype.every和Array.prototype.some的关系。
代码实现
实现思路和Promise.all类似。不过由于对异步过程的处理逻辑不同,因此这里的计数器用来标识是否所有的实例均 rejected。
Promise.any = function (iterators) {
const promises = Array.from(iterators);
const num = promises.length;
const rejectedList = new Array(num);
let rejectedNum = 0;
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
Promise.resolve(promise)
// 只要有一个 promise 执行成功,就返回。后面的 promise 就不管了
.then((value) => resolve(value))
.catch((error) => {
// 所有的 promise 执行失败,才算执行失败,并返回所有错误信息
rejectedList[index] = error;
if (++rejectedNum === num) {
reject(rejectedList);
}
});
});
});
};
实现 Promise.allSettled
Promise.allSettled(iterators)的传参和返回值与Promise.all相同。
根据ES2020,此返回的 promise 实例的状态只能是fulfilled。对于传入的所有 promise 实例,会等待每个 promise 实例结束,并且返回规定的数据格式。
如果传入 a、b 两个 promise 实例:a 变为 rejected,错误是 error1;b 变为 fulfilled,value 是 1。那么Promise.allSettled返回的 promise 实例的 value 就是:
[
{ status: "rejected", value: error1 },
{ status: "fulfilled", value: 1 },
];
代码实现
实现中的计数器,用于统计所有传入的 promise 实例。
const formatSettledResult = (success, value) =>
success
? { status: "fulfilled", value }
: { status: "rejected", reason: value };
Promise.allSettled = function (iterators) {
const promises = Array.from(iterators);
const num = promises.length;
const settledList = new Array(num);
let settledNum = 0;
return new Promise((resolve) => {
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
settledList[index] = formatSettledResult(true, value);
if (++settledNum === num) {
resolve(settledList);
}
})
.catch((error) => {
settledList[index] = formatSettledResult(false, error);
if (++settledNum === num) {
resolve(settledList);
}
});
});
});
};
实现 Promise.retry(promiseFn, times)
可以设置时间间隔和次数。
Promise.retry = function (promiseFn, times = 3) {
return new Promise(async (resolve, reject) => {
while (times--) {
try {
var ret = await promiseFn();
resolve(ret);
break;
} catch (error) {
if (!times) reject(error);
}
}
});
};
function getProm() {
const n = Math.random();
return new Promise((resolve, reject) => {
setTimeout(() => (n > 0.9 ? resolve(n) : reject(n)), 1000);
});
}
Promise.retry(getProm);
实现 Promise.all(list, limit)
控制一下子发出的请求个数。异步请求控制并发 LimitPromise
function PromiseLimit(funcArray, limit = 5) {
let i = 0;
// promise 存储
const result = [];
// 执行存储
const executing = [];
const queue = function () {
// 边界处理, funcArray 为空数组
if (i === funcArray.length) return Promise.all(executing);
// 一个 promise
const p = funcArray[i++]();
// 最终返回的 promise 队列
result.push(p);
// 在 p 执行结束之后,将 e 从 executing 数组中取出
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
// 执行中的 promise 队列
executing.push(e);
// 如果个数到了 limit, 先 race 等待执行结束,再执行 queue 添加一个 promise
if (executing.length >= limit) {
return Promise.race(executing).then(
() => queue(),
(e) => Promise.reject(e)
);
}
// 如果个数还没到,则直接执行 queue() 函数添加 promise
return Promise.resolve().then(() => queue());
};
return queue().then(() => Promise.all(result));
}
如果需要一个 add 函数用于添加 Promise。那么让我们来尝试实现它:
class PromiseLimit {
constructor(limit) {
// 定义限制数量
this.limit = limit;
// 定义一个队列用于存储 Promise
this.queue = [];
// 用于记录目前在进行中的 Promise 数量
this.pendingCount = 0;
}
add(fn) {
// 需要执行的任务入队列
this.queue.push(fn);
this.run();
}
run() {
while (this.pendingCount < this.limit && this.queue.length) {
// 如果当前进行中的 Promise 没有达到限制并且队列不为空,则选出队头任务执行
const fn = this.queue.shift();
// 将进行中的数量加 1
this.pendingCount++;
// 这里函数执行 Promise,并且无论成功还是失败,都对当前正在进行的数量减 1,并实现自调用进入下一个 Promise 的执行
fn().finally(() => {
this.pendingCount--;
this.run();
});
}
}
}
实现 Promise.all(list, limit)
- js 实现带并发限制的调度器,其实就是使用 promise 限制并发
- 实现一个可以控制请求并发数的最高效的发送请求功能。
- 手动控制并发请求 fetchWithLimit。 手动控制并发请求 fetchWithLimit 尽量快的实现
控制一下子发出的请求个数。异步请求控制并发 LimitPromise
function PromiseLimit(funcArray, limit = 5) {
let i = 0;
// promise 存储
const result = [];
// 执行存储
const executing = [];
const queue = function () {
// 边界处理, funcArray 为空数组
if (i === funcArray.length) return Promise.all(executing);
// 一个 promise
const p = funcArray[i++]();
// 最终返回的 promise 队列
result.push(p);
// 在 p 执行结束之后,将 e 从 executing 数组中取出
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
// 执行中的 promise 队列
executing.push(e);
// 如果个数到了 limit, 先 race 等待执行结束,再执行 queue 添加一个 promise
if (executing.length >= limit) {
return Promise.race(executing).then(
() => queue(),
(e) => Promise.reject(e)
);
}
// 如果个数还没到,则直接执行 queue() 函数添加 promise
return Promise.resolve().then(() => queue());
};
return queue().then(() => Promise.all(result));
}
实现一个并发限制功能。
function asyncPool(poolLimit, array, iteratorFn) {
let i = 0;
const ret = [];
const executing = [];
const enqueue = function () {
if (i === array.length) {
return Promise.resolve();
}
const item = array[i++];
const p = Promise.resolve().then(() => iteratorFn(item, array));
ret.push(p);
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
let r = Promise.resolve();
if (executing.length >= poolLimit) {
r = Promise.race(executing);
}
return r.then(() => enqueue());
};
return enqueue().then(() => Promise.all(ret));
}
实现 Promise.retry(promiseFn, times)
fetch 兼容超时重传, 可以设置时间间隔和次数。
Promise.retry = function (promiseFn, times = 3) {
return new Promise(async (resolve, reject) => {
while (times--) {
try {
var ret = await promiseFn();
resolve(ret);
break;
} catch (error) {
if (!times) reject(error);
}
}
});
};
function getProm() {
const n = Math.random();
return new Promise((resolve, reject) => {
setTimeout(() => (n > 0.9 ? resolve(n) : reject(n)), 1000);
});
}
Promise.retry(getProm);
用 promise 实现一个请求超时功能
function promiseWithTimeout(url, timeout = 3000) {
return new Promise((resolve, reject) => {
fetch(url)
.then((data) => data.json())
.then((data) => resolve(data)); // fetch 先得到结果就 resolve
setTimeout(() => reject(Error("time is out!")), timeout); // 时间到了还没 fetch 到就 reject
});
}
其他 Promise 相关的手写
顺序发送 4 个请求 a,b,c,d,要求按照顺序输出
顺序发送 4 个请求 a,b,c,d,要求按照顺序输出,即如果先返回 b,则不输出,再返回 a,输出 a,b
function getData(urls) {
return new Promise((resolve, reject) => {
const res = [],
len = urls.length;
urls.forEach((url, i) => {
fetch("http://localhost:8080" + url)
.then((data) => data.json())
.then((data) => {
res[i] = { data, printed: false }; // 将数据放入缓存数组
let flag = true;
for (let j = 0; j < len && flag; j += 1) {
if (res[j]) {
// 如果标志为 j 的有返回值,则继续
if (!res[j].printed) {
console.log(res[j].data);
res[j].printed = true;
j === len - 1 && resolve(res.map((o) => o.data));
}
} else {
// 无返回值,则跳出
flag = false;
}
}
}, reject);
});
});
}
const listPromise = getData([
"/data.json",
"/data2.json",
"/data3.json",
"/data4.json",
]);
listPromise.then((res) => console.log(res));
JS 实现一个带并发限制的异步调度器 Scheduler
完善代码中 Scheduler 类,使得以下程序能正确输出。
// 保证同时运行的任务最多有两个。
class Scheduler {
constructor() {
this.count = 2;
this.queue = [];
this.run = [];
}
add(task) {
// ...
}
}
const timeout = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time);
});
const scheduler = new Scheduler();
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then(() => console.log(order));
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
// output: 2 3 1 4
// 一开始,1、2两个任务进入队列
// 500ms时,2完成,输出2,任务3进队
// 800ms时,3完成,输出3,任务4进队
// 1000ms时,1完成,输出1
// 1200ms时,4完成,输出4
别人的解答:
class Scheduler {
constructor(count) {
this.count = 2;
this.queue = [];
this.run = [];
}
add(task) {
this.queue.push(task);
return this.schedule();
}
schedule() {
if (this.run.length < this.count && this.queue.length) {
const task = this.queue.shift();
const promise = task().then(() => {
this.run.splice(this.run.indexOf(promise), 1);
});
this.run.push(promise);
return promise;
} else {
return Promise.race(this.run).then(() => this.schedule());
}
}
}
手写 promisify 的实现
// 首先定一个需要进行 promisify 的函数
function asyncFn(a, b, callback) {
// 异步操作,使用 setTimeout 模拟
console.log("异步请求参数", a, b);
setTimeout(function () {
callback("异步请求结果");
}, 3000);
}
// 我们希望调用的方式是
const proxy = promisify(asyncFn);
proxy(11, 22).then((res) => {
// 此处输出异步函数执行结果
console.log(res);
});
// 定义一个方法, 需要针对异步方法做封装,所以需要一个入参,既需要promisify的原异步方法
function promisify(asyncFn) {
// 方法内部我们需要调用asyncFn方法,并传递原始参数,所以需要返回一个方法来接收参数
return function (...args) {
// 由于需要接收参数,所以参数我们可以写为...args
// 我们需要执行异步操作,并返回一个结果,所以返回一个 promise实例
return new Promise((resolve) => {
// asyncFn 需要执行一个回调,所以定义一个回调方法
const callback = function (...args) {
resolve(args);
};
args.push(callback);
asyncFn.apply(null, args);
});
};
}